Repository navigation
docs: add AI Teammates community example project - #5024
jackbridger wants to merge 1 commit into
Conversation
|
|
Note Currently processing new changes in this PR. This may take a few minutes, please wait... ⚙️ Run configuration
📒 Files selected for processing (3)
✨ Finishing Touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Hi @jackbridger, thanks for your interest in contributing! This project requires that pull request authors are vouched, and you are not in the list of vouched users. This PR will be closed automatically. See https://github.com/triggerdotdev/trigger.dev/blob/main/CONTRIBUTING.md for more details. |
| startSession: ({ chatId, clientData }) => startChatSession({ chatId, clientData }), | ||
| }); | ||
| const { messages, isLoading } = useLoadTranscript(chatId, loadTranscript, { transport }); | ||
| // Then useChat({ transport, messages }) once the transcript has loaded |
There was a problem hiding this comment.
🔴 Restored chats cannot send messages
After loading a transcript, useChat({ transport, messages }) generates a new ID instead of using chatId. startChatSession rejects that ID because the user owns the original chat, so sending fails.
| // Then useChat({ transport, messages }) once the transcript has loaded | |
| // Then useChat({ id: chatId, transport, messages }) once the transcript has loaded |
Was this helpful? React with 👍 or 👎 to provide feedback.
| chat.setUIMessageStreamOptions({ | ||
| messageMetadata: () => ({ botId: bot.id }), | ||
| }); |
There was a problem hiding this comment.
🟡 Handoff replies show the original teammate
When a group turn hands off, messageMetadata still stamps the initial bot.id on the reply. The UI identifies replies by that metadata, so it credits the wrong teammate.
Learn more
The group's handoff callback changes current inside the tools callback, but run captures a separate initial bot. Its metadata callback never observes a handoff. The UI uses that metadata to label the response. Changing the callback to consult a mutable variable requires sharing turn state with the tools callback and checking when the AI SDK emits metadata; metadata emitted at response start cannot be changed retroactively.
Example: A turn starts with Alice and invokes handoff to Bob. Bob's sandbox tools and instructions take over, but the reply remains labeled Alice.
Recommended fix: Coordinate the selected bot across the per-turn tools and run callbacks, and emit/update reply metadata when handoff completes. Test that a group turn with a handoff renders the answering bot's identity.
Was this helpful? React with 👍 or 👎 to provide feedback.
| await recordUsage( | ||
| owned.map(({ sandboxId, userId }) => ({ | ||
| userId, | ||
| kind: "sandbox" as const, | ||
| amount: INTERVAL_MINUTES * 60, | ||
| // A retried run meters the same moment again | ||
| idempotencyKey: `sandbox:${sandboxId}:${timestamp.toISOString()}`, | ||
| })) |
There was a problem hiding this comment.
🟡 New sandboxes incur full interval charges
If a sandbox starts just before the schedule runs, meterSandboxes charges it 300 seconds immediately. Its actual running time can be seconds, so the ledger overstates usage and can exhaust the quota early.
Learn more
Each run lists sandboxes currently running and inserts a fixed five-minute usage entry for each. The sample does not track when a sandbox started or how long it was running since the previous tick. The first tick after a sandbox starts therefore charges a full interval even if the sandbox just started, while a sandbox that starts and stops between ticks is not billed at all.
Example: A sandbox starts at 12:04:59 and is still running at the 12:05:00 tick. The ledger records 300 seconds although it ran for one second before the tick.
Recommended fix: Meter elapsed running time from sandbox start/stop events or durable per-sandbox timestamps; cap each tick's charge to the actual overlap with the interval, and record completed running periods that end between ticks.
Was this helpful? React with 👍 or 👎 to provide feedback.
|
|
||
| The `tools` callback runs each turn with the `chatId` and the client data. It loads the chat's bots, wires up the sandbox tools for the bot holding the turn, and in a group chat adds a `handoff` tool that moves the turn, and the sandbox, to a teammate: | ||
|
|
||
| ```ts trigger/chat.ts |
| id: "chat", | ||
| // Declared here so screenshots stay images when earlier turns are replayed | ||
| tools: async ({ chatId, clientData }) => { | ||
| const { chat: { kind, userId }, bots, bot } = await getChatBots(chatId, clientData?.botId); |
There was a problem hiding this comment.
🟥 Client-selected teammate risks cross-chat sandbox access
If getChatBots accepts a botId outside the owned chat, browser-controlled clientData.botId selects that bot's sandbox. The session's chat ownership check cannot protect a different bot's computer.
Was this helpful? React with 👍 or 👎 to provide feedback.
Summary
Adds a new example project page for AI Teammates, a community-built open-source Grok Bot clone by Code with Antonio: https://github.com/code-with-antonio/ai-teammates
The project uses a Trigger.dev
chat.agent()for each teammate's durable turn, a scheduled task to meter sandbox hours, Daytona sandboxes as each teammate's computer, and a Next.js frontend onuseTriggerChatTransport. The author has given permission for us to link it from our docs and examples.Changes
docs/guides/example-projects/ai-teammates.mdxcovering the chat agent (per-turn tools, handoffs in group chats, step and spend limits), the sandbox and memory tools, usage metering viaonTurnCompleteand a cron task, the Sentry init file with global hooks, and the frontend wiring. Code snippets are taken from the repo and link back to it.docs/docs.json.docs/guides/introduction.mdx.Companion PR in the examples repo: triggerdotdev/examples#154
🤖 Generated with Claude Code